// MobileTipper.cpp : Defines the entry point for the application.

#include "stdafx.h"
#include "MobileTipper.h"
#include <windowsx.h>

#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))

///////////////////////////////////////////////////////////////////////////////
// Tip datastructure

// This struct defines the instance variables required
// for each Tip
typedef struct __Tip
{
	double AmountOfBill;
    double TaxRate;
    double AmountOfSubtotal;
    double TipRate;
    int NumberInParty;
    double AmountOfTotal;
    double AmountOfTax;
    double AmountOfTip;
    double IndividualShare;
} Tip;

// Reset the contents of a Tip to sensible defaults
static void Tip_Clear(Tip * const pTip)
{
	pTip->AmountOfBill = 0.0;
	pTip->TaxRate = 0.0;
    pTip->AmountOfSubtotal = 0.0;
    pTip->TipRate = 0.0;
    pTip->NumberInParty = 1;
    pTip->AmountOfTotal = 0.0;
    pTip->AmountOfTip = 0.0;
    pTip->IndividualShare = 0.0;
}

// Calculate the totals for the bill from the
// provided information
static void Tip_Calculate(Tip * const pTip)
{
	pTip->AmountOfTax = pTip->AmountOfBill * pTip->TaxRate / 100;
	pTip->AmountOfSubtotal = pTip->AmountOfBill + pTip->AmountOfTax;

	pTip->AmountOfTip = pTip->AmountOfSubtotal * pTip->TipRate / 100;
	pTip->IndividualShare = pTip->AmountOfTip / pTip->NumberInParty;

	pTip->AmountOfTotal = pTip->AmountOfSubtotal + pTip->IndividualShare * pTip->NumberInParty;
}

static Tip CurrentTip;

///////////////////////////////////////////////////////////////////////////////
// Masked Edit Control (only allow currency related characters)

static LRESULT CALLBACK MaskedEditProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static TCHAR szAllowableChars[4096] = {0};

	// Fetch the old wnd proc which we stored away in the GWL_USERDATA field
	WNDPROC pfnOldWndProc = (WNDPROC)GetWindowLong(hWnd, GWL_USERDATA);

	// Process messages of interest
	if (uMsg == WM_CHAR)
	{
		// Is this the first key press in the lifetime of this app?
		if (szAllowableChars[0] == '\0')
		{
			// If so generate a list of special characters which
			// may be used in a currency or decimal value. We
			// cache this string to avoid performing these queries
			// for each and every key press.
#define APPEND_LOCALE_INFO(buffer, lctype) (GetLocaleInfo(lcid, lctype, &buffer[_tcslen(buffer)], ARRAY_SIZE(buffer) - _tcslen(buffer)))

			LCID lcid = GetUserDefaultLCID();
			APPEND_LOCALE_INFO(szAllowableChars, LOCALE_SCURRENCY);
			APPEND_LOCALE_INFO(szAllowableChars, LOCALE_SMONDECIMALSEP);
			APPEND_LOCALE_INFO(szAllowableChars, LOCALE_SMONTHOUSANDSEP);
			APPEND_LOCALE_INFO(szAllowableChars, LOCALE_SDECIMAL);
			APPEND_LOCALE_INFO(szAllowableChars, LOCALE_STHOUSAND);
			APPEND_LOCALE_INFO(szAllowableChars, LOCALE_SPOSITIVESIGN);
			APPEND_LOCALE_INFO(szAllowableChars, LOCALE_SNEGATIVESIGN);
			_tcscat(szAllowableChars, _T("%"));
		}

		// Is this an acceptable character?
		if (!iswdigit(wParam)  // digits
			&& !wcschr(szAllowableChars, wParam) // special symbols
			&& wParam != '\b') // backspace
		{
			// No - then stop it from being processed
			// by the original window procedure.
			return 0;
		}
	}

	// Allow the original window procedure to process
	// the message
	return CallWindowProc(pfnOldWndProc, hWnd, uMsg, wParam, lParam);
}

// Configure an edit control so that we can filter
// the allowable set of characters the user can
// type into the field.
static void SetupEditControl(HWND hDlg, UINT id)
{
	// Find the window handle for the edit control
	HWND hWndCtrl = GetDlgItem(hDlg, id);
	if (GetWindow(hWndCtrl, GW_CHILD) != 0)
		hWndCtrl = GetWindow(hWndCtrl, GW_CHILD);

	// Store the current window procedure in the
	// GWL_USERDATA field
	WNDPROC pfnOldWndProc = (WNDPROC)GetWindowLong(hWndCtrl, GWL_WNDPROC);
	SetWindowLong(hWndCtrl, GWL_USERDATA, (LONG)pfnOldWndProc);

	// Replace the window procedure with our one which
	// filters the allowable key presses
	SetWindowLong(hWndCtrl, GWL_WNDPROC, (LONG)MaskedEditProc);
}

///////////////////////////////////////////////////////////////////////////////
// Getter and Setter methods to parse and display
// Currency and Decimal values in a locale aware manor.

static int GetIntValue(HWND hDlg, UINT id)
{
	TCHAR szBuffer[2048];
	LCID lcid;
	LONG i;

	// Obtain the current text from the control
	GetDlgItemText(hDlg, id, szBuffer, ARRAY_SIZE(szBuffer));

	// Convert into an integer value
	lcid = GetUserDefaultLCID();
	VarI4FromStr(szBuffer, lcid, 0, &i);
	return i;
}

static double GetDecimalValue(HWND hDlg, UINT id)
{
	TCHAR szBuffer[2048];
	LCID lcid;
	double d;

	// Obtain the current text from the control
	// and remove the '%' symbol if present at
	// the end of the string
	GetDlgItemText(hDlg, id, szBuffer, ARRAY_SIZE(szBuffer));
	if (szBuffer[_tcslen(szBuffer) - 1] == '%')
		szBuffer[_tcslen(szBuffer) - 1] = '\0';

	// Convert into a decimal value
	lcid = GetUserDefaultLCID();
	VarR8FromStr(szBuffer, lcid, 0, &d);
	return d;
}

static double GetCurrencyValue(HWND hDlg, UINT id)
{
	return GetDecimalValue(hDlg, id);
}

static void SetIntValue(HWND hDlg, UINT id, int iValue)
{
	TCHAR szBuffer[32];

	_stprintf(szBuffer, _T("%d"), iValue);
	SetDlgItemText(hDlg, id, szBuffer);
}

static void SetCurrencyValue(HWND hDlg, UINT id, double fValue)
{
	// Format the decimal value in the standard C locale format
	TCHAR szBuffer1[32], szBuffer2[32];
	_stprintf(szBuffer1, _T("%f"), fValue);

	// Then call GetCurrencyFormat to convert it into
	// the format applicable to the user's current locale.
	GetCurrencyFormat(LOCALE_USER_DEFAULT, 0, szBuffer1, NULL, szBuffer2, ARRAY_SIZE(szBuffer2));

	// And place the text string into the control
	SetDlgItemText(hDlg, id, szBuffer2);
}

static void SetPercentageValue(HWND hDlg, UINT id, double fValue)
{
	// Format the decimal value in the standard C locale format
	TCHAR szBuffer1[32], szBuffer2[32];
	_stprintf(szBuffer1, _T("%f"), fValue);

	// Then call GetNumberFormat to convert it into
	// the format applicable to the user's current locale
	// and append a percentage sign.
	GetNumberFormat(LOCALE_USER_DEFAULT, 0, szBuffer1, NULL, szBuffer2, ARRAY_SIZE(szBuffer2));
	_tcscat(szBuffer2, _T("%"));

	// Then place the text string into the control
	SetDlgItemText(hDlg, id, szBuffer2);
}

///////////////////////////////////////////////////////////////////////////////
// Main Dialog

static void DisplayTip(HWND hDlg, Tip * const pTip)
{
	// Update the contents of the combo boxes and edit controls
	// to match the contents of the Tip data structure.
	SetCurrencyValue(hDlg, IDC_AMOUNTOFBILL, pTip->AmountOfBill);
	SetPercentageValue(hDlg, IDC_TAXRATE, pTip->TaxRate);
   	SetCurrencyValue(hDlg, IDC_AMOUNTOFSUBTOTAL, pTip->AmountOfSubtotal);

	SetPercentageValue(hDlg, IDC_TIPRATE, pTip->TipRate);
	SetCurrencyValue(hDlg, IDC_AMOUNTOFTOTAL, pTip->AmountOfTotal);
	SetIntValue(hDlg, IDC_NUMBERINPARTY, pTip->NumberInParty);

	SetCurrencyValue(hDlg, IDC_AMOUNTOFTAX, pTip->AmountOfTax);
	SetCurrencyValue(hDlg, IDC_AMOUNTOFTIP, pTip->AmountOfTip);
	SetCurrencyValue(hDlg, IDC_INDIVIDUALSHARE, pTip->IndividualShare);
}

static INT_PTR CALLBACK MobileTipperDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_INITDIALOG:
			{
                SHINITDLGINFO shidi;
				SHMENUBARINFO mbi;
				HWND hWndControl;
				TCHAR szBuffer[32];

                // Create a Done button and make the dialog fullscreen.  
                shidi.dwMask = SHIDIM_FLAGS;
                shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIZEDLGFULLSCREEN;
                shidi.hDlg = hDlg;
                SHInitDialog(&shidi);

				// Add a menu bar
                memset(&mbi, 0, sizeof(SHMENUBARINFO));
				mbi.cbSize     = sizeof(SHMENUBARINFO);
				mbi.hwndParent = hDlg;
				mbi.nToolBarId = IDR_MENU;
				mbi.hInstRes   = GetModuleHandle(NULL);
				mbi.dwFlags = SHCMBF_HMENU;
	            SHCreateMenuBar(&mbi);
				
				// Subclass the edit controls so we only
				// allowable characters acceptable in a
				// decimal or currency value.
				SetupEditControl(hDlg, IDC_AMOUNTOFBILL);
				SetupEditControl(hDlg, IDC_TAXRATE);
				SetupEditControl(hDlg, IDC_TIPRATE);
				SetupEditControl(hDlg, IDC_NUMBERINPARTY);

				// Add the options to the various combo boxes
				hWndControl = GetDlgItem(hDlg, IDC_TAXRATE); // Tax rate
				for (int i = 0; i < 13; i++)
				{
					_stprintf(szBuffer, _T("%d%%"), i);
					ComboBox_AddString(hWndControl, szBuffer);
				}

				hWndControl = GetDlgItem(hDlg, IDC_TIPRATE); // Tip Rate %
				for (int i = 1; i < 5; i++)
				{
					_stprintf(szBuffer, _T("%d%%"), i * 5);
					ComboBox_AddString(hWndControl, szBuffer);
				}

				hWndControl = GetDlgItem(hDlg, IDC_NUMBERINPARTY); // Number in party
				for (int i = 1; i < 13; i++)
				{
					_stprintf(szBuffer, _T("%d"), i);
					ComboBox_AddString(hWndControl, szBuffer);
				}

				// Initialise the TIP datastructure
				// and update the UI to match
				Tip_Clear(&CurrentTip);
				DisplayTip(hDlg, &CurrentTip);
            }
            return (INT_PTR)TRUE;

		case WM_CTLCOLORDLG:
		case WM_CTLCOLORSTATIC:
			{
				// Provide a blue background to the dialog
				HBRUSH brush = CreateSolidBrush(RGB(154, 228, 232));
				SetBkMode((HDC)wParam, TRANSPARENT);
				return (INT_PTR)brush;
			}

        case WM_COMMAND:
			switch (LOWORD(wParam))
			{
			case IDOK:
                EndDialog(hDlg, LOWORD(wParam));
                return (INT_PTR)TRUE;

			case ID_MENU_CLEAR:
				// Reset the TIP datastructure
				// and update the UI to match
				Tip_Clear(&CurrentTip);
				DisplayTip(hDlg, &CurrentTip);
				return (INT_PTR)TRUE;

			case ID_CALCULATE:
				{
					// Check that the values entered by the user
					// are sensible.
					TCHAR szBuffer[4096] = { 0 };
					if (GetCurrencyValue(hDlg, IDC_AMOUNTOFBILL) < 0)
						_tcscat(szBuffer, _T("Amount of bill must be greater than 0.00.\r\n"));
					if (GetDecimalValue(hDlg, IDC_TAXRATE) < 0)
						_tcscat(szBuffer, _T("Tax rate must be greater than 0.00%.\r\n"));
					if (GetDecimalValue(hDlg, IDC_TIPRATE) < 0)
						_tcscat(szBuffer, _T("Tip rate must be greater than 0.00%.\r\n"));
					if (GetIntValue(hDlg, IDC_NUMBERINPARTY) <= 0)
						_tcscat(szBuffer, _T("Number in party must be greater than 0.\r\n"));

					// If one or more errors were found display
					// an error message and don't calculate the tip
					if (_tcslen(szBuffer) > 0)
					{
						MessageBox(hDlg, szBuffer, _T("Alert!"), MB_OK | MB_ICONASTERISK);
						return (INT_PTR)TRUE;
					}
					
					// Otherwise update our TIP data structure,
					// calculate the tip and update the UI
					CurrentTip.AmountOfBill = GetCurrencyValue(hDlg, IDC_AMOUNTOFBILL);
					CurrentTip.TaxRate = GetDecimalValue(hDlg, IDC_TAXRATE);
					CurrentTip.TipRate = GetDecimalValue(hDlg, IDC_TIPRATE);
					CurrentTip.NumberInParty = GetIntValue(hDlg, IDC_NUMBERINPARTY);
	
					Tip_Calculate(&CurrentTip);
		            DisplayTip(hDlg, &CurrentTip);
				}
				return (INT_PTR)TRUE;
            }
            break;

        case WM_CLOSE:
            EndDialog(hDlg, message);
            return (INT_PTR)TRUE;

    }
    return (INT_PTR)FALSE;
}

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPTSTR    lpCmdLine,
                   int       nCmdShow)
{
	CoInitializeEx(NULL, COINIT_MULTITHREADED);

	DialogBox(hInstance, (LPCTSTR)IDD_MOBILETIPPER, NULL, MobileTipperDialog);
}